#include "hw_upy.h"
#include <rtthread.h>
#include <rtdevice.h>
#include <dfs_posix.h>

#define ARG_LENGTH_ERR { upy_set_err(e, ec_type, "invalid argument length");return VAL_UNDEFINED; }
#define ARG_TYPE_ERR   { upy_set_err(e, ec_type, "invalid argument length");return VAL_UNDEFINED; }

//upy_delay_ms(ms)
val_t upy_delay_ms(upy_t * e, val_t * p, int argc, val_t * v){
	if( argc != 1 ) ARG_LENGTH_ERR;
	int ms = val_2_integer(v);
	rt_thread_mdelay(ms);
    return VAL_UNDEFINED;
}

val_t upy_timeout_script = VAL_UNDEFINED;

static rt_err_t upy_timeout_cb(rt_device_t dev, rt_size_t size)
{
    return 0;
}

//timer_init(id)
val_t upy_timer_init(upy_t * e, val_t * p, int argc, val_t * v){
	if( argc != 1 ) ARG_LENGTH_ERR;
	if( !val_is_string(v) ) ARG_TYPE_ERR;
	
	upy_object_t * o = (upy_object_t *)val_2_intptr(p);
	const char * id = val_2_cstring(v);
	
	rt_device_t hw_dev = RT_NULL;
	hw_dev = rt_device_find(id);
    if (hw_dev == RT_NULL){
        rt_kprintf("can't find %s device!\n", id);
        return VAL_UNDEFINED;
    }
	o->ext_data = (intptr_t)hw_dev;
	return VAL_UNDEFINED;
}

//timer_setup(period, callback)
val_t upy_timer_setup(upy_t * e, val_t * p, int argc, val_t * v){
	if( argc != 3 ) ARG_LENGTH_ERR;
	if( !val_is_string(v) || !val_is_number(v + 1) || !val_is_script(v + 2)) ARG_TYPE_ERR;
	upy_object_t * o = (upy_object_t *)val_2_intptr(p);
	const char * id = val_2_cstring(v);
	int period = val_2_integer(v + 1);
	upy_timeout_script = *(v + 2);
	
	rt_err_t ret = RT_EOK;
	rt_device_t hw_dev = o->ext_data;
	if (hw_dev == RT_NULL){
		return VAL_UNDEFINED;
	}
	rt_hwtimerval_t timeout_s;
	ret = rt_device_open(hw_dev, RT_DEVICE_OFLAG_RDWR);
    if (ret != RT_EOK){
        rt_kprintf("open %s device failed!\n", id);
        return VAL_UNDEFINED;
    }
	rt_device_set_rx_indicate(hw_dev, upy_timeout_cb);
	rt_hwtimer_mode_t mode = HWTIMER_MODE_PERIOD;
	ret = rt_device_control(hw_dev, HWTIMER_CTRL_MODE_SET, &mode);
    if (ret != RT_EOK){
        rt_kprintf("set mode failed! ret is :%d\n", ret);
        return VAL_UNDEFINED;
    }
    timeout_s.sec = 0;
	timeout_s.usec = 1000 * period;

    if (rt_device_write(hw_dev, 0, &timeout_s, sizeof(timeout_s)) != sizeof(timeout_s))
    {
        rt_kprintf("set timeout value failed\n");
        return VAL_UNDEFINED;
    }
    return VAL_UNDEFINED;
}

//upy_fs_open(path, mode)
val_t upy_fs_open(upy_t * e, val_t * p, int argc, val_t * v){
    if( argc != 2 ) ARG_LENGTH_ERR;
	if( !val_is_string(v) )  ARG_TYPE_ERR;
	if( !val_is_string(v + 1) )  ARG_TYPE_ERR;
	const char * path = val_2_cstring(v);
	const char * mode = val_2_cstring(v + 1);
	
	int bmode = 0;
	if( !strstr(mode, "r") && !strstr(mode, "w") ) bmode |= O_RDWR;
	else if( !strstr(mode, "r") ) bmode |= O_RDONLY;
	else if( !strstr(mode, "w") ) bmode |= O_WRONLY;
	
	if( !strstr(mode, "c") ) bmode |= O_CREAT;
	if( !strstr(mode, "a") ) bmode |= O_APPEND;

	upy_object_t * o = (upy_object_t *)val_2_intptr(p);
	o->ext_data = (int)open(path, bmode);
	if( o->ext_data == NULL ) return VAL_FALSE;
	return VAL_TRUE;
}

//upy_fs_close()
val_t upy_fs_close(upy_t * e, val_t * p, int argc, val_t * v){
    if( argc != 0  ) ARG_LENGTH_ERR;
	upy_object_t * o = (upy_object_t *)val_2_intptr(p);
	if( o->ext_data == NULL ) return VAL_FALSE;
	close((int)o->ext_data);
	return VAL_UNDEFINED;
}

//upy_fs_read(buf, size)
val_t upy_fs_read(upy_t * e, val_t * p, int argc, val_t * v){
    if( argc != 2 ) ARG_LENGTH_ERR;
	if( !val_is_buffer(v) ) ARG_TYPE_ERR;
	if( !val_is_number(v + 1) ) ARG_TYPE_ERR;
	
	upy_object_t * o = (upy_object_t *)val_2_intptr(p);
	if( o->ext_data == NULL ) return VAL_FALSE;
	
	upy_buffer_t *buffer = (upy_buffer_t*)val_2_intptr(v);
	int size = val_2_integer(v + 1);
	if(size >= buffer->len) size = buffer->len;
	size = read((int)o->ext_data, buffer->buf, size);
	return val_mk_number(size);
}

//upy_fs_write(buf, size)
val_t upy_fs_write(upy_t * e, val_t * p, int argc, val_t * v){
    if( argc != 2 ) ARG_LENGTH_ERR;
	if( !val_is_buffer(v) ) ARG_TYPE_ERR;
	if( !val_is_number(v + 1) ) ARG_TYPE_ERR;
	
	upy_object_t * o = (upy_object_t *)val_2_intptr(p);
	if( o->ext_data == NULL ) return VAL_FALSE;
	
	upy_buffer_t *buffer = (upy_buffer_t*)val_2_intptr(v);
	int size = val_2_integer(v + 1);
	if(size >= buffer->len) size = buffer->len;
	size = write((int)o->ext_data, buffer->buf, size);
	return val_mk_number(size);
}

//upy_fs_seek(offset, whence)
val_t upy_fs_seek(upy_t * e, val_t * p, int argc, val_t * v){
    if( argc != 2 ) ARG_LENGTH_ERR;
	if( !val_is_number(v) ) ARG_TYPE_ERR;
	if( !val_is_string(v + 1) ) ARG_TYPE_ERR;
	
	upy_object_t * o = (upy_object_t *)val_2_intptr(p);
	if( o->ext_data == NULL ) return VAL_FALSE;
	
	int offset = val_2_integer(v);
	int mode = SEEK_SET;
	const char * whence = val_2_cstring(v + 1);
	if( !strcmp(whence, "set") ){
		mode = SEEK_SET;
	} else if( !strcmp(whence, "cur") ){
		mode = SEEK_CUR;
	} else if( !strcmp(whence, "end") ){
		mode = SEEK_END;
	} 
	return val_mk_number(lseek(o->ext_data, offset, mode));
}

//upy_uart_init(id)
val_t upy_uart_init(upy_t * e, val_t * p, int argc, val_t * v){
	if( argc != 1 ) ARG_LENGTH_ERR;
	if( !val_is_string(v) ) ARG_TYPE_ERR;
	upy_object_t * o = (upy_object_t *)val_2_intptr(p);
	const char * id = val_2_cstring(v);
	rt_device_t serial = rt_device_find(id);
	if (serial == RT_NULL){
		return VAL_UNDEFINED;
	}
	o->ext_data = serial;
	return VAL_UNDEFINED;
}

//upy_uart_open(baudrate, databits, stopbits, parity)
val_t upy_uart_open(upy_t * e, val_t * p, int argc, val_t * v){
	if( argc != 4 ) ARG_LENGTH_ERR;
	if( !val_is_integer(v) ) ARG_TYPE_ERR;
	if( !val_is_integer(v + 1) ) ARG_TYPE_ERR;
	if( !val_is_integer(v + 2) ) ARG_TYPE_ERR;
	if( !val_is_string(v + 3) ) ARG_TYPE_ERR;

	upy_object_t * o = (upy_object_t *)val_2_intptr(p);
	rt_device_t serial = o->ext_data;
	if( serial == NULL ) return VAL_FALSE;
	
	int baudrate = val_2_integer(v);
	int databits = val_2_integer(v + 1);
	int stopbits = val_2_double(v + 2);
	char * parity = val_2_cstring(v + 3);
	
    if (rt_device_open(serial, RT_DEVICE_FLAG_INT_RX) != RT_EOK){
        return VAL_UNDEFINED;
    }
	
	struct serial_configure config = RT_SERIAL_CONFIG_DEFAULT; 
	
	config.baud_rate = baudrate;
	config.data_bits = databits;
	config.stop_bits = stopbits;
	config.parity = PARITY_NONE;
	if( !strcmp(parity, "none") ){
		config.parity = PARITY_NONE;
	} else if( !strcmp(parity, "odd") ){
		config.parity = PARITY_ODD;
	} else if( !strcmp(parity, "even") ){
		config.parity = PARITY_EVEN;
	}
	rt_device_control(serial, RT_DEVICE_CTRL_CONFIG, &config);
	return VAL_TRUE;
}

//upy_uart_read(buffer, size)
val_t upy_uart_read(upy_t * e, val_t * p, int argc, val_t * v){
	if( argc != 2 ) ARG_LENGTH_ERR;
	if( !val_is_buffer(v) ) ARG_TYPE_ERR;
	if( !val_is_integer(v + 1) ) ARG_TYPE_ERR;
	upy_object_t * o = (upy_object_t *)val_2_intptr(p);
	if( o->ext_data == NULL ) return VAL_FALSE;
	upy_buffer_t * buffer = val_2_intptr(v);
	int size = val_2_integer(v + 1);
	if(size > buffer->len ) size = buffer->len;
	return val_mk_number(rt_device_read((rt_device_t)o->ext_data, 0, buffer->buf, size));
}

//upy_uart_write(buffer, size)
val_t upy_uart_write(upy_t * e, val_t * p, int argc, val_t * v){
	if( argc != 2 ) ARG_LENGTH_ERR;
	if( !val_is_buffer(v) ) ARG_TYPE_ERR;
	if( !val_is_integer(v + 1) ) ARG_TYPE_ERR;
	upy_object_t * o = (upy_object_t *)val_2_intptr(p);
	if( o->ext_data == NULL ) return VAL_FALSE;
	upy_buffer_t * buffer = val_2_intptr(v);
	int size = val_2_integer(v + 1);
	if(size > buffer->len ) size = buffer->len;
	return val_mk_number(rt_device_write((rt_device_t)o->ext_data, 0, buffer->buf, size));
}

//upy_uart_close()
val_t upy_uart_deinit(upy_t * e, val_t * p, int argc, val_t * v){
	if( argc != 0 ) ARG_LENGTH_ERR;
	upy_object_t * o = (upy_object_t *)val_2_intptr(p);
	if( o->ext_data != NULL) {
		if( rt_device_close((rt_device_t)o->ext_data) == RT_EOK )
			return VAL_TRUE;
	}
	return VAL_FALSE;
}

upy_class_t * upy_class_timer(upy_t * e){
	upy_class_t * cls = upy_class_create(e, 2);
    upy_class_prop_set(e, cls, 0, "__init__", val_mk_native(upy_timer_init));
    upy_class_prop_set(e, cls, 1, "setup", val_mk_native(upy_timer_setup));
	return cls;
}


upy_class_t * upy_class_fs(upy_t * e){
	upy_class_t * cls = upy_class_create(e, 5);
    upy_class_prop_set(e, cls, 0, "open", val_mk_native(upy_fs_open));
    upy_class_prop_set(e, cls, 1, "close", val_mk_native(upy_fs_close));
	upy_class_prop_set(e, cls, 2, "read", val_mk_native(upy_fs_read));
	upy_class_prop_set(e, cls, 3, "write", val_mk_native(upy_fs_write));
	upy_class_prop_set(e, cls, 4, "seek", val_mk_native(upy_fs_seek));
	return cls;
}

upy_class_t * upy_class_uart(upy_t * e){
	upy_class_t * cls = upy_class_create(e, 3);
    upy_class_prop_set(e, cls, 0, "__init__", val_mk_native(upy_uart_init));
    upy_class_prop_set(e, cls, 1, "read", val_mk_native(upy_uart_read));
	upy_class_prop_set(e, cls, 2, "write", val_mk_native(upy_uart_write));
	return cls;
}

int upy_modules(upy_t * e){
	int index = 0;
	upy_object_t * o = upy_module_create(e, "upy", 4);
	
	upy_object_prop_set(e, o, index++, "delay_ms", val_mk_native(upy_delay_ms));
	upy_object_prop_set(e, o, index++, "Timer", val_mk_class( upy_class_timer(e) ));
	upy_object_prop_set(e, o, index++, "Uart", val_mk_class( upy_class_uart(e) ));
	upy_object_prop_set(e, o, index++, "File", val_mk_class( upy_class_fs(e) ));
    return e->err;
}
